package com.vvv.shortlink.project.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.vvv.shortlink.common.convention.excepton.ClientException;
import com.vvv.shortlink.common.convention.excepton.ServiceException;
import com.vvv.shortlink.project.dao.entity.LinkAccessLogsDO;
import com.vvv.shortlink.project.dao.entity.LinkAccessStatsDO;
import com.vvv.shortlink.project.dao.mapper.*;
import com.vvv.shortlink.project.dto.req.LinkStatsAccessRecordReqDTO;
import com.vvv.shortlink.project.dto.req.LinkStatsReqDTO;
import com.vvv.shortlink.project.dto.req.SelectUvTypeDTO;
import com.vvv.shortlink.project.dto.resp.*;
import com.vvv.shortlink.project.service.LinkStatsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @author vvv
 * @date 2024/2/21 17 48
 * discription
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class LinkStatsServiceImpl implements LinkStatsService {

    private final LinkAccessStatsMapper linkAccessStatsMapper;
    private final LinkLocaleStatsMapper linkLocaleStatsMapper;
    private final LinkAccessLogsMapper linkAccessLogsMapper;
    private final LinkBrowserStatsMapper linkBrowserStatsMapper;
    private final LinkOsStatsMapper linkOsStatsMapper;
    private final LinkNetworkStatsMapper linkNetworkStatsMapper;
    private final LinkDeviceStatsMapper linkDeviceStatsMapper;

    private final ThreadPoolExecutor monitorStatsThreadPoolExecutor;

    /**
     * 50ms
     * @param requestParam
     * @return
     */
    @Override
    public LinkStatsRespDTO oneShortLinkStatsFutureVersion(LinkStatsReqDTO requestParam) {
        // 1. 参数校验
        Long userId = requestParam.getUserId();
        String fullShortUrl = requestParam.getFullShortUrl();
        String startDate = requestParam.getStartDate();
        String endDate = requestParam.getEndDate();
        if (StringUtils.isAllEmpty(startDate, endDate)) {
           requestParam.setStartDate(DateUtil.today());
           requestParam.setEndDate(DateUtil.today());
           startDate = requestParam.getStartDate();
           endDate = requestParam.getEndDate();
        }
        if (DateUtil.betweenDay(DateUtil.parse(startDate), DateUtil.parse(endDate), true) > 30) {
            throw new ClientException("相隔天数不能超过一个月");
        }
        if (userId == null || StringUtils.isEmpty(fullShortUrl)) {
            throw new ClientException("参数不能为空");
        }

        // 2.总pv uv uip
        CompletableFuture<LinkAccessStatsDO> totalPvUvUipResultFuture = CompletableFuture.supplyAsync(
                () -> linkAccessStatsMapper.getTotalPvUvUipInRangeTime(requestParam),
                monitorStatsThreadPoolExecutor
        );

        // 3. 每天访问量
        String finalStartDate = startDate;
        String finalEndDate = endDate;
        CompletableFuture<List<LinkStatsAccessDailyRespDTO>> dailyResultFuture = CompletableFuture.supplyAsync(() -> {
            List<LinkStatsAccessDailyRespDTO> dailyResult = new ArrayList<>();
            // 获取开始结束的全部日期
            List<String> rangeDates = DateUtil.rangeToList(DateUtil.parse(finalStartDate), DateUtil.parse(finalEndDate), DateField.DAY_OF_MONTH).stream()
                    .map(DateUtil::formatDate)
                    .toList();
            Map<String, List<LinkStatsAccessDailyRespDTO>> dailyDataMap = linkAccessStatsMapper.listAccessStatsGroupByDate(requestParam)
                    .stream()
                    .collect(Collectors.groupingBy(LinkStatsAccessDailyRespDTO::getDate));
            for (String rangeDate : rangeDates) {
                if (dailyDataMap.containsKey(rangeDate)) {
                    dailyResult.add(dailyDataMap.get(rangeDate).get(0));
                } else {
                    LinkStatsAccessDailyRespDTO noDailyData = LinkStatsAccessDailyRespDTO.builder()
                            .uv(0)
                            .pv(0)
                            .uip(0)
                            .date(rangeDate)
                            .build();
                    dailyResult.add(noDailyData);
                }
            }
            return dailyResult;
        }, monitorStatsThreadPoolExecutor);

        // 4. 地区（国内）访问量
        CompletableFuture<List<LinkStatsLocaleCNRespDTO>> localResultFuture = CompletableFuture.supplyAsync(() -> {
            List<LinkStatsLocaleCNRespDTO> localResult = linkLocaleStatsMapper.listLocalStatsGroupByProvince(requestParam);
            int totalLocalCnt = localResult.stream()
                    .mapToInt(LinkStatsLocaleCNRespDTO::getCnt)
                    .sum();
            for (LinkStatsLocaleCNRespDTO each : localResult) {
                double ratio = (double) each.getCnt() / totalLocalCnt;
                double actualRatio = Math.round(ratio * 100.0) / 100.0;
                each.setRatio(actualRatio);
            }
            return localResult;
        }, monitorStatsThreadPoolExecutor);

        // 5. 小时访问量
        CompletableFuture<List<Integer>> hoursResultFuture = CompletableFuture.supplyAsync(() -> {
            List<Integer> hoursResult = new ArrayList<>();
            //  查询开始结束时间内的以小时分组的总pv数据
            Map<Integer, List<LinkAccessStatsDO>> hourDataMap = linkAccessStatsMapper.listAccessStatsGroupByHour(requestParam)
                    .stream()
                    .collect(Collectors.groupingBy(LinkAccessStatsDO::getHour));
            for (int i = 0; i < 24; i++) {
                if (hourDataMap.containsKey(i)) {
                    hoursResult.add(hourDataMap.get(i).get(0).getPv());
                } else {
                    hoursResult.add(0);
                }
            }
            return hoursResult;
        }, monitorStatsThreadPoolExecutor);

        // 6. 高频ip访问情况
        CompletableFuture<List<LinkStatsTopIpRespDTO>> topIpResultFuture = CompletableFuture.supplyAsync(
                () -> linkAccessLogsMapper.listTopIpStatsGroupByIp(requestParam),
                monitorStatsThreadPoolExecutor
        );

        // 7. 获取在指定日期内的一周访问情况
        CompletableFuture<List<Integer>> weekResultFuture = CompletableFuture.supplyAsync(() -> {
            List<Integer> weekResult = new ArrayList<>();
            Map<Integer, List<LinkAccessStatsDO>> weekDataMap = linkAccessStatsMapper.listAccessStatsGroupByWeekday(requestParam)
                    .stream()
                    .collect(Collectors.groupingBy(LinkAccessStatsDO::getWeekday));
            for (int i = 1; i <= 7; i++) {
                if (weekDataMap.containsKey(i)) {
                    weekResult.add(weekDataMap.get(i).get(0).getPv());
                } else {
                    weekResult.add(0);
                }
            }
            return weekResult;
        }, monitorStatsThreadPoolExecutor);

        // 8. 获取浏览器在指定日期内的访问量
        CompletableFuture<List<LinkStatsBrowserRespDTO>> browserResultFuture = CompletableFuture.supplyAsync(() -> {
            List<LinkStatsBrowserRespDTO> browserResult = linkBrowserStatsMapper.listBrowserStatsGroupByBrowser(requestParam);
            int totalBrowserCnt = browserResult.stream()
                    .mapToInt(LinkStatsBrowserRespDTO::getCnt)
                    .sum();
            for (LinkStatsBrowserRespDTO each : browserResult) {
                double ratio = (double) each.getCnt() / totalBrowserCnt;
                double actualRatio = Math.round(ratio * 100.0) / 100.0;
                each.setRatio(actualRatio);
            }
            return browserResult;
        }, monitorStatsThreadPoolExecutor);

        // 9. 获取操作系统在指定日期内的访问量
        CompletableFuture<List<LinkStatsOsRespDTO>> osResultFuture = CompletableFuture.supplyAsync(() -> {
            List<LinkStatsOsRespDTO> osResult = linkOsStatsMapper.listOsStatsGroupByOs(requestParam);
            int totalOsCnt = osResult.stream()
                    .mapToInt(LinkStatsOsRespDTO::getCnt)
                    .sum();
            for (LinkStatsOsRespDTO each : osResult) {
                double ratio = (double) each.getCnt() / totalOsCnt;
                double actualRatio = Math.round(ratio * 100.0) / 100.0;
                each.setRatio(actualRatio);
            }
            return osResult;
        }, monitorStatsThreadPoolExecutor);

        // 10. 获取网络在指定日期内的访问量
        CompletableFuture<List<LinkStatsNetworkRespDTO>> networkResultFuture = CompletableFuture.supplyAsync(() -> {
            List<LinkStatsNetworkRespDTO> networkResult = linkNetworkStatsMapper.listNetworkStatsGroupByNetwork(requestParam);
            int totalNetworkCnt = networkResult.stream()
                    .mapToInt(LinkStatsNetworkRespDTO::getCnt)
                    .sum();
            for (LinkStatsNetworkRespDTO each : networkResult) {
                double ratio = (double) each.getCnt() / totalNetworkCnt;
                double actualRatio = Math.round(ratio * 100.0) / 100.0;
                each.setRatio(actualRatio);
            }
            return networkResult;
        }, monitorStatsThreadPoolExecutor);

        // 11. 获取访问设备在指定日期内的访问量
        CompletableFuture<List<LinkStatsDeviceRespDTO>> deviceResultFuture = CompletableFuture.supplyAsync(() -> {
            List<LinkStatsDeviceRespDTO> deviceResult = linkDeviceStatsMapper.listDeviceStatsGroupByDevice(requestParam);
            int totalDeviceCnt = deviceResult.stream()
                    .mapToInt(LinkStatsDeviceRespDTO::getCnt)
                    .sum();
            for (LinkStatsDeviceRespDTO each : deviceResult) {
                double ratio = (double) each.getCnt() / totalDeviceCnt;
                double actualRatio = Math.round(ratio * 100.0) / 100.0;
                each.setRatio(actualRatio);
            }
            return deviceResult;
        }, monitorStatsThreadPoolExecutor);

        // 12. 获取新老客户在指定日期内的访问量
        CompletableFuture<List<LinkStatsUvRespDTO>> oldAndNewUserResultFuture = CompletableFuture.supplyAsync(() -> {
            List<LinkStatsUvRespDTO> oldAndNewUserResult = linkAccessLogsMapper.listOldAndNewUserStats(requestParam);
            int oldUserCnt = 0;
            int newUserCnt = 0;
            for (LinkStatsUvRespDTO each : oldAndNewUserResult) {
                if (each.getUvType().equals("old_user")) {
                    oldUserCnt = each.getCnt();
                } else {
                    newUserCnt = each.getCnt();
                }
            }
            int totalOldAndNewUserCnt = oldUserCnt + newUserCnt;
            double oldRatio = (double) oldUserCnt / totalOldAndNewUserCnt;
            double actualOldRatio = Math.round(oldRatio * 100.0) / 100.0;
            double newRatio = (double) newUserCnt / totalOldAndNewUserCnt;
            double actualNewRatio = Math.round(newRatio * 100.0) / 100.0;
            for (LinkStatsUvRespDTO each : oldAndNewUserResult) {
                if (each.getUvType().equals("old_user")) {
                    each.setRatio(actualOldRatio);
                } else {
                    each.setRatio(actualNewRatio);
                }
            }
            return oldAndNewUserResult;
        }, monitorStatsThreadPoolExecutor);

        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
                totalPvUvUipResultFuture,
                dailyResultFuture,
                localResultFuture,
                hoursResultFuture,
                topIpResultFuture,
                weekResultFuture,
                browserResultFuture,
                osResultFuture,
                networkResultFuture,
                deviceResultFuture,
                oldAndNewUserResultFuture
        );
        try {
            allFutures.get();

            LinkStatsRespDTO build = LinkStatsRespDTO.builder()
                    .pv(totalPvUvUipResultFuture.get().getPv())
                    .uv(totalPvUvUipResultFuture.get().getUv())
                    .uip(totalPvUvUipResultFuture.get().getUip())
                    .daily(dailyResultFuture.get())
                    .localeCnStats(localResultFuture.get())
                    .hourStats(hoursResultFuture.get())
                    .topIpStats(topIpResultFuture.get())
                    .weekdayStats(weekResultFuture.get())
                    .browserStats(browserResultFuture.get())
                    .osStats(osResultFuture.get())
                    .uvTypeStats(oldAndNewUserResultFuture.get())
                    .deviceStats(deviceResultFuture.get())
                    .networkStats(networkResultFuture.get())
                    .build();
            return build;
        } catch (Exception e) {
            log.error(e.toString());
            Thread.currentThread().interrupt();
            throw new ServiceException("单个短链监控异步编排错误");
        }
    }

    @Override
    public IPage<LinkStatsAccessRecordRespDTO> shortLinkStatsAccessRecord(LinkStatsAccessRecordReqDTO requestParam) {
        Long userId = requestParam.getUserId();
        String fullShortUrl = requestParam.getFullShortUrl();
        String startDate = requestParam.getStartDate();
        String endDate = requestParam.getEndDate();
        if (StringUtils.isAnyEmpty(fullShortUrl, startDate, endDate) || userId == null) {
            throw new ClientException("参数不能为空");
        }
        LambdaQueryWrapper<LinkAccessLogsDO> queryWrapper = Wrappers.lambdaQuery(LinkAccessLogsDO.class)
                .eq(LinkAccessLogsDO::getUserId, userId)
                .eq(LinkAccessLogsDO::getFullShortUrl, fullShortUrl)
                .between(LinkAccessLogsDO::getCreateTime, startDate, endDate);
        IPage<LinkAccessLogsDO> linkAccessLogsDOIPage =  linkAccessLogsMapper.selectPage(requestParam, queryWrapper);
        IPage<LinkStatsAccessRecordRespDTO> result = linkAccessLogsDOIPage.convert(each -> {
            LinkStatsAccessRecordRespDTO temp = BeanUtil.toBean(each, LinkStatsAccessRecordRespDTO.class);
            temp.setLocale(each.getLocal());
            return temp;
        });
        // 填充是新访客还是老访客
        List<String> uvList = result.getRecords().stream()
                .map(LinkStatsAccessRecordRespDTO::getUser)
                .toList();
        Map<String, String> uvTypeMap = linkAccessLogsMapper.selectUvTypeByUsers(
                userId,
                fullShortUrl,
                startDate,
                endDate,
                uvList
        ).stream().collect(Collectors.toMap(SelectUvTypeDTO::getUser, SelectUvTypeDTO::getUvType, (oldValue, newValue) -> newValue));
        result.getRecords().forEach(each -> {
            String uvType = uvTypeMap.get(each.getUser());
            each.setUvType(uvType);
        });
        return result;
    }


    /**
     * 200ms
     * @param requestParam
     * @return
     */
    @Override
    public LinkStatsRespDTO oneShortLinkStats(LinkStatsReqDTO requestParam) {
        // 1. 参数校验
        Long userId = requestParam.getUserId();
        String fullShortUrl = requestParam.getFullShortUrl();
        String startDate = requestParam.getStartDate();
        String endDate = requestParam.getEndDate();
        if (userId == null || StringUtils.isEmpty(fullShortUrl)) {
            throw new ClientException("参数不能为空");
        }

        // 2.获取指定日期内的总pv uv uip
        LinkAccessStatsDO totalPvUvUipResult = linkAccessStatsMapper.getTotalPvUvUipInRangeTime(requestParam);
        log.error(totalPvUvUipResult.toString());

        // 3. 获取指定日期内的每天访问量
        List<LinkStatsAccessDailyRespDTO> dailyResult = new ArrayList<>();
        // 获取开始结束的全部日期
        List<String> rangeDates = DateUtil.rangeToList(DateUtil.parse(startDate), DateUtil.parse(endDate), DateField.DAY_OF_MONTH).stream()
                .map(DateUtil::formatDate)
                .toList();
        // 查询开始结束时间内的基础数据
        Map<String, List<LinkStatsAccessDailyRespDTO>> dailyDataMap = linkAccessStatsMapper.listAccessStatsGroupByDate(requestParam)
                .stream()
                .collect(Collectors.groupingBy(LinkStatsAccessDailyRespDTO::getDate));
        for (String rangeDate : rangeDates) {
            if (dailyDataMap.containsKey(rangeDate)) {
                dailyResult.add(dailyDataMap.get(rangeDate).get(0));
            } else {
                LinkStatsAccessDailyRespDTO noDailyData = LinkStatsAccessDailyRespDTO.builder()
                        .uv(0)
                        .pv(0)
                        .uip(0)
                        .date(rangeDate)
                        .build();
                dailyResult.add(noDailyData);
            }
        }
        log.error(dailyResult.toString());

        // 4. 获取在指定日期内的地区（国内）访问量
        List<LinkStatsLocaleCNRespDTO> localResult = linkLocaleStatsMapper.listLocalStatsGroupByProvince(requestParam);
        int totalLocalCnt = localResult.stream()
                .mapToInt(LinkStatsLocaleCNRespDTO::getCnt)
                .sum();
        for (LinkStatsLocaleCNRespDTO each : localResult) {
            double ratio = (double) each.getCnt() / totalLocalCnt;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            each.setRatio(actualRatio);
        }
        log.error(localResult.toString());

        // 5. 获取在指定日期内的小时访问量
        List<Integer> hoursResult = new ArrayList<>();
        //  查询开始结束时间内的以小时分组的总pv数据
        Map<Integer, List<LinkAccessStatsDO>> hourDataMap = linkAccessStatsMapper.listAccessStatsGroupByHour(requestParam)
                .stream()
                .collect(Collectors.groupingBy(LinkAccessStatsDO::getHour));
        for (int i = 0; i < 24; i++) {
            if (hourDataMap.containsKey(i)) {
                hoursResult.add(hourDataMap.get(i).get(0).getPv());
            } else {
                hoursResult.add(0);
            }
        }
        log.error(hoursResult.toString());

        // 6. 获取在指定日期内的高频ip访问情况
        List<LinkStatsTopIpRespDTO> topIpResult = linkAccessLogsMapper.listTopIpStatsGroupByIp(requestParam);
        log.error(topIpResult.toString());

        // 7. 获取在指定日期内的一周访问情况
        List<Integer> weekResult = new ArrayList<>();
        Map<Integer, List<LinkAccessStatsDO>> weekDataMap = linkAccessStatsMapper.listAccessStatsGroupByWeekday(requestParam)
                .stream()
                .collect(Collectors.groupingBy(LinkAccessStatsDO::getWeekday));
        for (int i = 1; i <= 7; i++) {
            if (weekDataMap.containsKey(i)) {
                weekResult.add(weekDataMap.get(i).get(0).getPv());
            } else {
                weekResult.add(0);
            }
        }
        log.error(weekResult.toString());

        // 8. 获取浏览器在指定日期内的访问量
        List<LinkStatsBrowserRespDTO> browserResult = linkBrowserStatsMapper.listBrowserStatsGroupByBrowser(requestParam);
        int totalBrowserCnt = browserResult.stream()
                .mapToInt(LinkStatsBrowserRespDTO::getCnt)
                .sum();
        for (LinkStatsBrowserRespDTO each : browserResult) {
            double ratio = (double) each.getCnt() / totalBrowserCnt;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            each.setRatio(actualRatio);
        }
        log.error(browserResult.toString());

        // 9. 获取操作系统在指定日期内的访问量
        List<LinkStatsOsRespDTO> osResult = linkOsStatsMapper.listOsStatsGroupByOs(requestParam);
        int totalOsCnt = osResult.stream()
                .mapToInt(LinkStatsOsRespDTO::getCnt)
                .sum();
        for (LinkStatsOsRespDTO each : osResult) {
            double ratio = (double) each.getCnt() / totalOsCnt;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            each.setRatio(actualRatio);
        }
        log.error(osResult.toString());

        // 10. 获取网络在指定日期内的访问量
        List<LinkStatsNetworkRespDTO> networkResult = linkNetworkStatsMapper.listNetworkStatsGroupByNetwork(requestParam);
        int totalNetworkCnt = networkResult.stream()
                .mapToInt(LinkStatsNetworkRespDTO::getCnt)
                .sum();
        for (LinkStatsNetworkRespDTO each : networkResult) {
            double ratio = (double) each.getCnt() / totalNetworkCnt;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            each.setRatio(actualRatio);
        }
        log.error(networkResult.toString());

        // 11. 获取访问设备在指定日期内的访问量
        List<LinkStatsDeviceRespDTO> deviceResult = linkDeviceStatsMapper.listDeviceStatsGroupByDevice(requestParam);
        int totalDeviceCnt = deviceResult.stream()
                .mapToInt(LinkStatsDeviceRespDTO::getCnt)
                .sum();
        for (LinkStatsDeviceRespDTO each : deviceResult) {
            double ratio = (double) each.getCnt() / totalDeviceCnt;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            each.setRatio(actualRatio);
        }
        log.error(deviceResult.toString());

        // 12. 获取新老客户在指定日期内的访问量
        List<LinkStatsUvRespDTO> oldAndNewUserResult = linkAccessLogsMapper.listOldAndNewUserStats(requestParam);
        int oldUserCnt = 0;
        int newUserCnt = 0;
        for (LinkStatsUvRespDTO each : oldAndNewUserResult) {
            if (each.getUvType().equals("old_user")) {
                oldUserCnt = each.getCnt();
            } else {
                newUserCnt = each.getCnt();
            }
        }
        int totalOldAndNewUserCnt = oldUserCnt + newUserCnt;
        double oldRatio = (double) oldUserCnt / totalOldAndNewUserCnt;
        double actualOldRatio = Math.round(oldRatio * 100.0) / 100.0;
        double newRatio = (double) newUserCnt / totalOldAndNewUserCnt;
        double actualNewRatio = Math.round(newRatio * 100.0) / 100.0;
        for (LinkStatsUvRespDTO each : oldAndNewUserResult) {
            if (each.getUvType().equals("old_user")) {
                each.setRatio(actualOldRatio);
            } else {
                each.setRatio(actualNewRatio);
            }
        }
        log.error(oldAndNewUserResult.toString());

        return LinkStatsRespDTO.builder()
                .pv(totalPvUvUipResult.getPv())
                .uv(totalPvUvUipResult.getUv())
                .uip(totalPvUvUipResult.getUip())
                .daily(dailyResult)
                .localeCnStats(localResult)
                .hourStats(hoursResult)
                .topIpStats(topIpResult)
                .weekdayStats(weekResult)
                .browserStats(browserResult)
                .osStats(osResult)
                .uvTypeStats(oldAndNewUserResult)
                .deviceStats(deviceResult)
                .networkStats(networkResult)
                .build();
    }
}